
open Ast
open Ast_fullfill
open Tools_identifier

module StrMap = Map.Make(String)

type context = {
	co_idents : string StrMap.t;
	co_labels : string StrMap.t
}

let get_value ident map =
	if StrMap.mem ident map then
		StrMap.find ident map
	else ident
		
let rec inline_program (refPath,identsMap,labelsMap,declIdents,labels,absolute) sourceElements =
	let fullfill = fullfill_list fullfill_sourceElement {
		idents = StrSet.empty;
		labels = StrSet.empty;
		locals = StrSet.empty;
		} sourceElements in
	let identsMap,declIdents,_ = StrSet.fold (fun ident map -> free_ident map ident false) fullfill.idents (identsMap,declIdents,absolute) in
	let labelsMap,labels,_ = StrSet.fold (fun label map -> free_ident map label false) fullfill.labels (labelsMap,labels,labels) in
	let stmts = List.map (fun srcElement -> match srcElement with
		| AStatement stmt -> (inline_statement {
			co_idents = identsMap;
			co_labels = labelsMap
		} refPath stmt)
		(* | AFunctionDeclaration*) (* Modif #1*) ) sourceElements in
		declIdents, labels, stmts
	
and inline_sourceElement map refPath = function
	| AStatement stmt -> AStatement (inline_statement map refPath stmt)
(*| AFunctionDeclaration funcDec -> AFunctionDeclaration (inline_functionDeclaration map refPath funcDec) *) (* Modif #1 *)

and inline_statement map refPath = function
	| ABlock block -> ABlock (inline_block map refPath block)
	| AVariableStatement vs -> AVariableStatement (inline_variableStatement map refPath vs)
	| AConstStatement cs -> AConstStatement (inline_constStatement map refPath cs)
	| AFuncDeclaration fd -> AFuncDeclaration (inline_functionDeclaration map refPath fd)	(* Modif #1 *)
	| AEmptyStatement -> AEmptyStatement
	| AExpressionStatement es -> AExpressionStatement (inline_expressionStatement map refPath es)
	| AIfStatement is -> AIfStatement (inline_ifStatement map refPath is)
	| AIterationStatement is -> AIterationStatement (inline_iterationStatement map refPath is)
	| AContinueStatement cs -> AContinueStatement (inline_continueStatement map refPath cs)
	| ABreakStatement bs -> ABreakStatement (inline_breakStatement map refPath bs)
	| AReturnStatement rs -> inline_returnStatement map refPath rs
	| AWithStatement ws -> AWithStatement (inline_withStatement map refPath ws)
	| ALabelledStatement ls -> ALabelledStatement (inline_labelledStatement map refPath ls)
	| ASwitchStatement ss -> ASwitchStatement (inline_switchStatement map refPath ss)
	| AThrowStatement ts -> AThrowStatement (inline_expression map refPath ts)
	| ATryStatement ts -> ATryStatement (inline_tryStatement map refPath ts)
	| ADebuggerStatement -> ADebuggerStatement

and inline_continueStatement map refPath = function
	| None -> None
	| Some ident -> Some (get_value ident map.co_labels)

and inline_breakStatement map refPath = function
	| None -> None
	| Some ident -> Some (get_value ident map.co_labels)

and inline_block map refPath stmts = 
	List.map (inline_statement map refPath) stmts

and inline_variableStatement map refPath (b,c,vs) =
	b,c,(List.map (inline_variableDeclaration map refPath) vs)
	
and inline_constStatement map refPath (b,c,cs) =
	b,c,(List.map (inline_variableDeclaration map refPath) cs)

and inline_variableDeclaration map refPath (ident,init) =
	match init with
		| None -> (get_value ident map.co_idents),None
		| Some init -> (get_value ident map.co_idents), Some (inline_assignmentExpression map refPath init)

and inline_functionDeclaration map refPath (is_private,is_meta,is_inline,ident,params,body) =
	let newIdent = get_value ident map.co_idents in
	let map = {
		co_idents = StrMap.add ident newIdent map.co_idents;
		co_labels = map.co_labels
	} in
	let params, body = inline_functionContent map refPath params body in
	is_private,is_meta,is_inline,newIdent,params,body

and inline_functionExpression map refPath (is_inline,ident,params,body) =
	let map,newIdent = (match ident with
		| None ->  map,None
		| Some ident -> let newIdent = get_value ident map.co_idents in
		let map = {
			co_idents = StrMap.add ident newIdent map.co_idents;
			co_labels = map.co_labels
		} in
		map, (Some newIdent)
	) in
	let params, body = inline_functionContent map refPath params body in
	is_inline,newIdent,params,body
	
and inline_functionContent map refPath params body =
	let fullfill = fullfill_list fullfill_sourceElement {
		idents = List.fold_right (fun i set -> StrSet.add i set ) params StrSet.empty;
		labels = StrSet.empty;
		locals = StrSet.empty
		} body in
	let idents = StrSet.fold (fun ident map -> StrMap.add ident ident map) fullfill.idents map.co_idents in
	let idents = if StrMap.mem "?" idents then StrMap.remove "?" idents else idents in
	params, (inline_body {
		co_idents = idents;
		co_labels = map.co_labels
	} refPath body)

and inline_body map refPath body =
	List.map (inline_sourceElement map refPath) body

and inline_expressionStatement map refPath expr =
	inline_expression map refPath expr

and inline_ifStatement map refPath = function
	| AIfElse (expr, stmt1, stmt2) -> AIfElse ((inline_expression map refPath expr), (inline_statement map refPath stmt1), (inline_statement map refPath stmt2))
	| AIf (expr, stmt) -> AIf ((inline_expression map refPath expr), (inline_statement map refPath stmt))

and inline_iterationStatement map refPath = function
	| ADoWhile (stmt, expr) -> ADoWhile ((inline_statement map refPath stmt), (inline_expression map refPath expr))
	| AWhile (expr, stmt) -> AWhile ((inline_expression map refPath expr), (inline_statement map refPath stmt))
	| AFor (expr1, expr2, expr3, stmt) -> AFor (
		(inline_expressionOption map refPath expr1), 
		(inline_expressionOption map refPath expr2), 
		(inline_expressionOption map refPath expr3),
		(inline_statement map refPath stmt)
		)
	| AForVar (is_meta, varDecls, expr1, expr2, stmt) -> AForVar (
		is_meta,
		(List.map (inline_variableDeclaration map refPath) varDecls),
		(inline_expressionOption map refPath expr1),
		(inline_expressionOption map refPath expr2),
		(inline_statement map refPath stmt)
		)
	| AForIn (lhs, expr, stmt) -> AForIn(
		(inline_leftHandSideExpression map refPath lhs),
		(inline_expression map refPath expr),
		(inline_statement map refPath stmt)
		)
	| AForVarIn (is_meta, varDecl, expr, stmt) -> AForVarIn (
		is_meta,
		(inline_variableDeclaration map refPath varDecl),
		(inline_expression map refPath expr),
		(inline_statement map refPath stmt)
		)
	
and inline_expressionOption map refPath = function
	| None -> None
	| Some expr -> Some (inline_expression map refPath expr)

and inline_returnStatement map refPath expr = 
	if StrMap.mem "?" map.co_idents then
		let break = ABreakStatement (Some (StrMap.find "-" map.co_labels)) in
		(match expr with
			| None -> break
			| Some expr -> 
				ABlock(
					(AExpressionStatement([
							AAssignOp(
								(ANewExpression(
									AMemberExpression(APrimaryExpr(
									AIdentifier(StrMap.find "?" map.co_idents)
								)))),
								"=",
								(match expr with
									| [expr] -> inline_assignmentExpression map refPath expr
									| _ -> ACondExpr(ACondBinaryExpr(AUnaryExpr(
									APostfixExpr(ALeftHandSide(ANewExpression(
									AMemberExpression(APrimaryExpr(AExpression(
									inline_expression map refPath expr
									))))))))))
					)]))::break::[]
				)
		)
	else
		AReturnStatement (inline_expressionOption map refPath expr)

and inline_tryStatement map refPath = function
	| ATryCatch (block, catch) -> ATryCatch ((inline_block map refPath block), (inline_catch map refPath catch))
	| ATryFinally (block, finally) -> ATryFinally ((inline_block map refPath block), (inline_block map refPath finally))
	| ATryCatchFinally (block, catch, finally) -> ATryCatchFinally ((inline_block map refPath block), (inline_catch map refPath catch), (inline_block map refPath finally))

and inline_catch map refPath (ident,block) = 
	(get_value ident map.co_idents), (inline_block map refPath block)

and inline_withStatement map refPath (expr,stmt) = 
	(inline_expression map refPath expr), (inline_statement map refPath stmt)

and inline_labelledStatement map refPath (ident,stmt) =
	(get_value ident map.co_labels), (inline_statement map refPath stmt)

and inline_switchStatement map refPath (expr,caseBlock) =
	(inline_expression map refPath expr), (inline_caseBlock map refPath caseBlock)
	
and inline_caseBlock map refPath = function
	| ACasesNoDefault caseClauses -> ACasesNoDefault (List.map (inline_caseClause map refPath) caseClauses)
	| ACasesDefault (caseClauses1, defaultClause, caseClauses2) -> ACasesDefault (
		(List.map (inline_caseClause map refPath) caseClauses1),
		(List.map (inline_statement map refPath) defaultClause),
		(List.map (inline_caseClause map refPath) caseClauses2))

and inline_caseClause map refPath (expr,stmts) =
	(inline_expression map refPath expr), (List.map (inline_statement map refPath) stmts)
	
and inline_expression map refPath exprs =
	List.map (inline_assignmentExpression map refPath) exprs

and inline_assignmentExpression map refPath = function
	| ACondExpr expr -> ACondExpr (inline_conditionalExpression map refPath expr)
	| AAssignOp (lhs,s,expr) ->
		AAssignOp ((inline_leftHandSideExpression map refPath lhs),s, (inline_assignmentExpression map refPath expr))

and inline_conditionalExpression map refPath = function
	| ACondBinaryExpr expr -> ACondBinaryExpr (inline_binaryExpression map refPath expr)
	| ACondTernaryExpr (expr1, expr2, expr3) -> ACondTernaryExpr (
		(inline_binaryExpression map refPath expr1),
		(inline_assignmentExpression map refPath expr2),
		(inline_assignmentExpression map refPath expr3))

and inline_binaryExpression map refPath = function
	| AUnaryExpr expr -> AUnaryExpr (inline_unaryExpression map refPath expr)
	| ABinaryExpr (s, expr1, expr2) -> ABinaryExpr (s, (inline_binaryExpression map refPath expr1), (inline_binaryExpression map refPath expr2))

and inline_unaryExpression map refPath = function
	| APostfixExpr expr -> APostfixExpr (inline_postfixExpression map refPath expr)
	| AOpUnaryExpr (s,expr) -> AOpUnaryExpr (s, (inline_unaryExpression map refPath expr))

and inline_postfixExpression map refPath = function
	| ALeftHandSide lhs -> ALeftHandSide (inline_leftHandSideExpression map refPath lhs)
	| ALeftHandSideOp (lhs,s) -> ALeftHandSideOp ((inline_leftHandSideExpression map refPath lhs), s)

and inline_leftHandSideExpression map refPath = function
	| ANewExpression expr -> ANewExpression (inline_newExpression map refPath expr)
	| ACallExpression expr -> ACallExpression (inline_callExpression map refPath expr)

and inline_callExpression map refPath = function
	| ACallMemberArgs (expr, args) -> ACallMemberArgs ((inline_memberExpression map refPath expr), (inline_arguments map refPath args))
	| ACallExpressionArgs (expr, args) -> ACallExpressionArgs ((inline_callExpression map refPath expr), (inline_arguments map refPath args))
	| ACallExpressionExpr (expr1, expr2) -> ACallExpressionExpr ((inline_callExpression map refPath expr1), (inline_expression map refPath expr2))
	| ACallExpressionIdent (expr, s) -> ACallExpressionIdent ((inline_callExpression map refPath expr), s)

and inline_arguments map refPath args = 
	List.map (inline_assignmentExpression map refPath) args

and inline_newExpression map refPath = function
	| AMemberExpression expr -> AMemberExpression (inline_memberExpression map refPath expr)
	| ANewMemberExpression expr -> ANewMemberExpression (inline_newExpression map refPath expr)

and inline_memberExpression map refPath = function
	| APrimaryExpr(AThis) -> refPath
	| APrimaryExpr expr -> APrimaryExpr (inline_primaryExpression map refPath expr)
	| AFunctionExpr expr -> AFunctionExpr (inline_functionExpression map refPath expr)
	| AMemberBracketExpr (expr1, expr2) -> AMemberBracketExpr ((inline_memberExpression map refPath expr1), (inline_expression map refPath expr2))
	| AMemberDotExpr (expr, s) -> AMemberDotExpr ((inline_memberExpression map refPath expr), s)
	| AMemberNewExpr (expr, args) -> AMemberNewExpr ((inline_memberExpression map refPath expr), (inline_arguments map refPath args))

and inline_primaryExpression map refPath = function
	| AThis -> AExpression [ACondExpr(ACondBinaryExpr(AUnaryExpr(APostfixExpr(ALeftHandSide(ANewExpression(AMemberExpression(refPath)))))))]
	| AIdentifier ident -> AIdentifier (get_value ident map.co_idents)
	| ALiteral literal -> ALiteral literal
	| AArrayLiteral l -> AArrayLiteral (inline_arrayLiteral map refPath l)
	| AObjectLiteral l -> AObjectLiteral (inline_objectLiteral map refPath l)
	| AExpression expr -> AExpression (inline_expression map refPath expr)

and inline_arrayLiteral map refPath = function
	| AElisions elisions -> AElisions elisions
	| AElements elements -> AElements (List.map (inline_element map refPath) elements)
	| AElementsAndElisions (elements, elisions) -> AElementsAndElisions ((List.map (inline_element map refPath) elements), elisions)

and inline_element map refPath (elisions,expr) =
	elisions, (inline_assignmentExpression map refPath expr)
	
and inline_objectLiteral map refPath props =
	List.map (inline_propertyAssignment map refPath) props

and inline_propertyAssignment map refPath = function
	| APropAssignment (propName, expr) -> APropAssignment (propName, inline_assignmentExpression map refPath expr)
	| APropGet (propName,body) -> 
		let _, body = inline_functionContent map refPath [] body in
		APropGet (propName, body)
	| APropSet (propName, params, body) -> 
		(*let map = {
			co_idents = List.fold_left (fun map ident -> StrMap.add ident ident map) map.co_idents params;
			co_labels = map.co_labels
		} in*)
		let params, body = inline_functionContent map refPath params body in
		APropSet (propName, params, body)

